package hotgammon.common;

import java.util.HashMap;

/** Skeleton implementation of HotGammon.

   This source code is from the book 
     "Flexible, Reliable Software:
       Using Patterns and Agile Development"
     published 2010 by CRC Press.
   Author: 
     Henrik B Christensen 
     Computer Science Department
     Aarhus University

   This source code is provided WITHOUT ANY WARRANTY either 
   expressed or implied. You may study, use, modify, and 
   distribute it for non-commercial purposes. For any 
   commercial use, see http://www.baerbak.com/
 */

public class GameImpl implements Game {
	private Color turn;
	private int[] lastDices;
	private int[] unusedValues;
	private int countTurns;
	private MoveStrategy moveStrategy;
	boolean gameStarted;
	boolean gameEnded;
	
	/**
	 * Determine the next values of the dices in the roll
	 * 0 --> 1,2
	 * 1 --> 3,4
	 * 2 --> 5,6
	 */
	private int dicesState;
	
	private HashMap<Location, Integer> locationCheckerColorHashMap = new HashMap<Location, Integer>();
	private ExtraTurnStrategy extraTurnStrategy;

	//constructor for GameImpl
	//before game starts, Black checkers and Red checkers are placed as show in the book
	//'2' means 2 Black checkers and '-5' means 5 Red checkers in respective Location
	public GameImpl(GameFactory factory){
		this.moveStrategy = factory.createMoveStrategy();
		this.extraTurnStrategy = factory.createExtraTurnStrategy();
		this.gameEnded = false;
		locationCheckerColorHashMap.put(Location.R1, 2);
		locationCheckerColorHashMap.put(Location.R6, -5);
		locationCheckerColorHashMap.put(Location.R8, -3);
		locationCheckerColorHashMap.put(Location.R12, 5);
		locationCheckerColorHashMap.put(Location.B1, -2);
		locationCheckerColorHashMap.put(Location.B6, 5);
		locationCheckerColorHashMap.put(Location.B8, 3);
		locationCheckerColorHashMap.put(Location.B12, -5);

		locationCheckerColorHashMap.put(Location.R2, 0);
		locationCheckerColorHashMap.put(Location.R3, 0);
		locationCheckerColorHashMap.put(Location.R4, 0);
		locationCheckerColorHashMap.put(Location.R5, 0);
		locationCheckerColorHashMap.put(Location.R7, 0);
		locationCheckerColorHashMap.put(Location.R9, 0);
		locationCheckerColorHashMap.put(Location.R10, 0);
		locationCheckerColorHashMap.put(Location.R11, 0);

		locationCheckerColorHashMap.put(Location.B2, 0);
		locationCheckerColorHashMap.put(Location.B3, 0);
		locationCheckerColorHashMap.put(Location.B4, 0);
		locationCheckerColorHashMap.put(Location.B5, 0);
		locationCheckerColorHashMap.put(Location.B7, 0);
		locationCheckerColorHashMap.put(Location.B9, 0);
		locationCheckerColorHashMap.put(Location.B10, 0);
		locationCheckerColorHashMap.put(Location.B11, 0);
	}
	/** Reset the entire game to start from scratch. 
	 * No player is in turn, and the game awaits a
	 * call to nextTurn to start a game.
	 */
	public void newGame() {
		turn = Color.NONE;
		lastDices = new int[2];
		dicesState = 0;
		unusedValues = new int[0];
		countTurns = 0;//number of turns set to zero when new game starts
		gameStarted = false;
	}
	
	public void alterTurn() {
		if (turn == Color.RED)
			turn = Color.BLACK;
		else
			turn = Color.RED;
	}
	
	/** Tell the game instance that the user wants the game to
	 * progress after the 'turn has finished' i.e. the player in
	 * turn has exhausted his/her abilities for moving checkers.
	 * The game responds by changing to the other player and rolling
	 * the dice. At the beginning of the game (after 'newGame')
	 * the dice are rolled and depending on the roll, the game
	 * progresses. For Alphagamon, Black always starts.
	 * 
	 * PRECONDITION: The player in turn has indeed exhausted the
	 * his/her ability to make moves.
	 */
	public void nextTurn() {
		if (countTurns == 6) {
			gameEnded = true;
			return;
		}
		
		//gameStarted = false before and after 1,2 is thrown
		//            and is set to true when 3,4 is thrown
		if (gameStarted) {
			if (extraTurnStrategy.extra(lastDices)) {
				//an extra turn is granted, dont alter the turn
			} else {
				alterTurn();
			}
		}
		
		gameStarted = true;
		if (turn == Color.NONE) {
			lastDices[0] = 1;
			lastDices[1] = 2;
			turn = Color.BLACK;
			dicesState = 1;
			gameStarted = false;
		} else if (dicesState == 1) {
			lastDices[0] = 3;
			lastDices[1] = 4;
			dicesState = 2;
		} else if (dicesState == 2){
			lastDices[0] = 5;
			lastDices[1] = 6;
			dicesState = 0;
		} else if (dicesState == 0){
			lastDices[0] = 1;
			lastDices[1] = 2;
			dicesState = 1;
		}
		countTurns++;
		if (gameStarted) {
			unusedValues = lastDices.clone();
			sortDescending(unusedValues);
		}
	}
	private void sortDescending(int[] array) {
		for(int i = 0; i < array.length; i++) {
			for(int j = i+1; j < array.length; j++) {
				if (array[i] < array[j]) {
					int tmp = array[i];
					array[i] = array[j];
					array[j] = tmp;
				}
			}
		}
	}
	/** move one checker from one location to another. If the move is
	 * invalid, then no change is made on the board. For Alphamon, the move
	 * is invalid if the location has one or more of opponents checkers
	 * @param from the location to move the checker from
	 * @param to the location to move the checker to
	 * @return false if the indicated move is illegal 
	 */
	public boolean move(Location from, Location to) {
		boolean isValid = moveStrategy.isMoveValid(from, to, this);
		if (!isValid) {
			return false;
		}
		
		int absoluteDist = Math.abs(Location.distance(from, to));
		
		int[] newUnusedValues = new int[unusedValues.length - 1];
		//remove it from the unused list
		int i, j = 0;
		boolean removed = false;
		for(i = 0; i < unusedValues.length; i++) {
			if (!removed && unusedValues[i] == absoluteDist) {
				removed = true;
			} else {
				newUnusedValues[j++] = unusedValues[i];
			}
		}
		unusedValues = newUnusedValues;
		
		Color toColor = getColor(to);
		Color fromColor = getColor(from);
		
		//check for blot condition
		if (toColor != Color.NONE && toColor != fromColor) {
			//blot: move all checkers at destination pile to the corresponding bar
			Location bar = (toColor == Color.RED ? Location.R_BAR : Location.B_BAR);
			
			locationCheckerColorHashMap.put(bar, 
					(getCount(bar) + 1) * toColor.getSign());
			locationCheckerColorHashMap.put(to, 0);
		}
		
		//move the single checker to the destination square
		locationCheckerColorHashMap.put(from, locationCheckerColorHashMap.get(from) - turn.getSign());
		locationCheckerColorHashMap.put(to, locationCheckerColorHashMap.get(to) + turn.getSign());
		return true;
	}
	/** return the color of the player that is in turn i.e. is allowed
	 *  to make a move.
	 * @return the color of the player to move next. If no player is in
	 * turn (before the game is started), NONE is returned.
	 */  
	public Color getPlayerInTurn() {
		return turn;
	}
	/** return the number of moves left in the current turn. I.e. if a
	 * new turn has just begun and the dice rolled are [1,2] the return
	 * value is 2. 
	 * @return the number of moves left for the player-in-turn subject
	 * to the rules of backgammon.
	 */  
	public int getNumberOfMovesLeft() {
		if (turn == Color.NONE || winner() != Color.NONE)
			return 0;
		else
			return unusedValues.length;
	}
	/** return an integer array of size exactly 2 containing the
	 * values of the dice thrown.
	 * for alphamon, dice sequences are {1-2),{3-4},{5-6} 
	 * @return array of two integers defining the dice values rolled last.
	 */
	public int[] diceThrown() { 
		return lastDices;
	} 
	/** return an integer array of size in range [0;4] containing
	 * die values that have not yet been used to move a checker.
	 * POSTCONDITION: The array is sorted so the largest die value
	 * is first in the array.
	 * @return int array of unused die values.
	 */
	public int[] diceValuesLeft() {
		int[] result = new int[unusedValues.length];
		for (int i = 0; i < result.length; i++) {
			result[i] = unusedValues[i];
		}
		return result;
	}
	/** return the winner of this game. 
	 * @return the winner of the game; if game is still in progress, then
	 * Color.NONE is returned.  After 6 rolls of dice, winner is RED
	 */
	public Color winner() {
		if(gameEnded){
			return Color.RED;
		}
		else return Color.NONE;
	}

	/** get the colour of the checkers on a given location.
	 * @param location the location on the board to access
	 * @return the color of the checkers on this location
	 */
	public Color getColor(Location location) {
		int numOfCheckerInLocation;
		numOfCheckerInLocation = locationCheckerColorHashMap.get(location);
		if(numOfCheckerInLocation >0){//positive count, return BLACK
			return Color.BLACK;
		}
		else if(numOfCheckerInLocation < 0){//negative count, return RED
			return Color.RED;
		}
		else 
			return Color.NONE;
	}
	/** get the count of checkers of this location.
	 * @param location the location to inspect
	 * @return a integer value showing the number of checkers on this location.
	 */
	public int getCount(Location location) { 
		if (locationCheckerColorHashMap.get(location) == null)
			return 0;
		else
			return Math.abs(locationCheckerColorHashMap.get(location));
	}
}
